14.1 代码块
说明:
代码块
是由C语言实现的,是对C语言中函数的扩展。
支持的语言:Objective-C
、C
、C++
、Objective-C++
用途:替代函数
或实现闭包
现状:代码块
在Xcode
的GCC
和CLang
工具中是有效的,但它不属于ANSI
的C
语言标准。关于代码块的提议已经提交给C语言标准团体。
14.1.1 代码块和函数指针
说明:
代码块
的语法借鉴了函数指针
- 返回类型可以手动声明也可以由编译器通过代码块推导
- 具有指定类型的参数列表
- 拥有名称
- 代码放在
{}
中语法:
<returntype> (^blockname)(list of arguments) = ^(arguments){body;}
实现部分推导出返回值类型
1 | // 实现部分省略了返回值类型,没有参数列表 |
1 | /** |
14.1.1.1 通过代码块名调用代码块
说明:可以像调用
函数
一样调用代码块
。
比函数强大:代码块可以访问与它相同的有效范围内声明的变量。
1 | // 声明并初始化变量:声明时的作用域和代码块相同 |
14.1.1.2 直接调用代码块(匿名)
说明:使用代码块时通常不需要创建一个代码块变量,而是在代码中内联代码块的内容。
使用场景:作为参数传递给方法或函数
1 | // 数组 |
14.1.1.3 使用typedef
关键字
说明:将
代码块
声明定义为一种类型,更易于代码的编写。
语法:typedef 代码块定义;
注意:typedef
后面的代码块
定义中的代码块名不再具备代码块名
的功能,而是一种类型名。
1 | // 将代码块定义为一种类型: MKSampleMultiply2BlockRef |
14.1.1.4 代码块和变量
说明:代码块被声明后会捕捉到创建时的上下文中的变量或函数。
- 全局变量(包括在封闭范围内声明的本地静态变量)
- 全局函数
- 封闭范围内的参数
Objective-C
的实例变量- 代码块内部的本地变量
本地变量
说明:与代码块在同一范围内声明的变量。
捕获情况:代码块会在定义时把本地变量当作常量
复制并保存它们的状态。
1 | // 定义代码块类型 |
全局变量
说明:可以根据需要将变量标记为
静态的(全局的)
。
捕获情况:同本地变量。
1 | static double a = 10, b = 20; |
参数变量
说明:代码块中的参数变量与函数中的参数变量具有相同的作用。
1 | // 定义代码块类型 |
_block变量
关键字:
_block
说明:本地变量
会被代码块
当作常量
获取到,如果想要修改它们的值,必须通过_black
将它们声明为可修改
的。
限制:由两种情况不能使用_block
修饰
- 长度可变数组
- 包含长度可变数组的结构体
1 | // 用_block 修饰,使变量 c 在代码块中的副本可修改 |
代码块内部的本地变量
说明:对
代码块来说
,和本地变量
一样使用。
1 | // 定义并实现代码块 |
14.1.2 Objective-C代码块内存管理
说明:代码块是对象,所以可以向它发送任何与内存管理由关的信息。
- 如果引用了一个
Objective-C
对象,必须要保留
它- 如果类的方法中的
代码块
通过引用访问了一个实例变量,要保留
一次self
(执行所在方法的对象)- 如果通过数值访问了一个实例变量,变量需要
保留
ProcessString.h:方法中包含代码块的类
1 |
|
ProcessString.m
1 |
|
main.m
1 |
|
扩展:在C语言中,必须使用
Block_copy()
和Block_release()
函数来适当地管理内存。
1 |
|
14.2 并发性
说明:能够在同一时间执行多个任务的程序称为
并发的(concurrent)
程序。苹果公司提供了多种可以利用多核特性的API
。
相关技术选择 | 说明 | 备注 |
---|---|---|
POSIX线程 | 利用并发行最基础的方法是使用POSIX 线程来处理程序的不同部分使其能够独立运行。POSIX线程 拥有支持C 和Objective-C 的API。 |
因为线程是级别较低的API,必须手动管理,挑战很大 |
GDC(Grand Central Dipatch) | 运行在系统级别,减少了不少线程管理的麻烦 | 可以平衡应用程序所有内同的家在,从而提高计算机或设备的运行效率 |
14.2.1 同步
关键字:
@synchronized
说明:用来设置临界区
,确保多个线程不会在同一时间进入临界区
。
相关:@property
指令的atomic
特性会让编译器通过插入@synchronize(mutex, atomic)
生成强制彼此互斥的getter
和setter
方法(降低了代码性能),而nonatomic
特性(默认)则不会。
14.2.1.1 选择性能
说明:
NSObject
提供了一些可以使代码在后台以较低性能运行的方法(方法名带有performSelector
前缀)
performSelectorInBackground实例方法
说明:通过创建一个线程,在后端运行一个指定的方法。
限制:指定运行的方法(第一个参数)要遵从以下限制
- 方法中需要
@autoreleasepool
- 方法不能有返回值,参数最多一个且必须为
id
类型
- (void) myMethod;
- (void) myMethod:(id)myObject;
原型:
NSObject
1 | /** |
SelectorTester.h
1 |
|
SelectorTester.m
1 |
|
14.2.1.2 调度队列
说明:
GDC
可以使用调度队列(dispatch queue)
,共有3种。
队列类型 | 说明 | 并行/串行 | 备注 |
---|---|---|---|
连续队列 | 根据指派的顺序执行任务 | 串行,先入先出(FIFO,栈) | 可以创建多个连续 队列,彼此并行 |
并发队列 | 并发执行一个或多个任务 | 并行,根据指派到队列的顺序开始执行 | 无法创建,只能从系统提供的并发队列中选择(一共3个) |
主队列 | 应用程序的有效的主队列 | 主线程只有一个,无所谓串/并行 | 执行的应用程序的主线程任务 |
调度队列数据类型:
dispatch_queue_t
连续队列
说明:只要任务是异步提交的,队列会确保任务根据预定顺序执行,不会发生死锁。
适用:一连串的任务需要按照一定的顺序执行的场景
dispatch_queue_create全局方法
说明:创建连续队列
原型:
1 | /** |
1 | // 声明连续队列 |
并发队列
说明:并发调度队列适用于那些可以并行运行的任务
- 开始执行时间遵从FIFO
- 任务可以在前一个任务结束前就开始执行
- 一次所运行的任务数量是无法预测的(根据其它运行的任务的状况)
技巧:如果需要确保每次运行的任务的数量都是一样的,可以通过线程
API
来手动管理线程。
dispatch_get_global_queue全局方法
说明:获取系统的并发队列。
原型:/usr/include/dispatch/queue.h
1 | /** |
1 | // 声明并发队列 |
主队列
dispatch_get_current_queue全局方法
说明:获取
当前运行的
队列代码块,如果在代码块的对象之外调用了这个函数,则它会返回主队列
。
注意:该方法在从OS X 10.9
和ios 6
开始被废弃,因为GCD队列本身是不可重入的,同步阻塞会导致死锁。
用途:仍然可以作为调试手段在代码中使用。
1 | // 获取主线程或当前队列 |
14.2.2 队列也要内存管理
说明:
调度队列
是引用计数对象,可以使用dispatch_retain()
和dispatch_release()
来修改队列的保留计数器的值。
14.2.2.1 队列的上下文
说明:可以向
调度对象(包括调度队列)
指派全局数据上下文
,可以在上下文中指派任意类型的数据,比如Objective-C
对象或指针。
内存管理:必须在需要队列上下文
的时候分配内存并在队列销毁之前进行清理。
dispatch_set_context全局方法
说明:为指定队列设置
全局上下文
。
原型:/usr/include/dispatch/object.h
1 | /** |
dispatch_get_context全局方法
说明:获得
调度队列
的全局数据上下文
。
原型:/usr/include/dispatch/object.h
1 | /** |
1 | // 创建可变字典(作为全局数据上下文) |
14.2.2.2 全局数据上下文内存管理
说明:编写一个
终结器(finalizer)
函数,在dealloc
中调用。
1 | /** |
14.2.2.3 向调度队列添加任务
说明:有两种方式可以向队列中添加任务,每种方式针对
代码块
和函数
各有一个调度函数(共4个)
- 同步:队列会一直等待前面任务结束
- 异步:添加任务后,不必等待任务,函数会立刻返回(推荐,因为不会阻塞其他代码的运行)
|**|同步|异步|
|代码块|dispatch_sync
|dispatch_async
|
|函数|dispatch_sync_f
|dispatch_async_f
|
dispatch_sync全局函数
说明:向队列中
同步
添加代码块
。
原型:/use/include/dispatch/queue.h
1 | /** |
dispatch_async全局函数
说明:向队列中
异步
添加代码块
。
原型:/use/include/dispatch/queue.h
1 | /** |
1 | // 异步添加代码块:内联方式 |
dispatch_sync_f全局函数
说明:向队列中
同步
添加代码块
。
原型:/use/include/dispatch/queue.h
1 | /** |
dispatch_async_f全局函数
说明:向队列中
异步
添加代码块
。
原型:/use/include/dispatch/queue.h
1 | /** |
1 | // 定义好要添加到队列的函数 |
1 | // 同步向队列中添加函数 |
14.2.2.3 调度队列的暂停和重启
dispatch_suspend全局方法
说明:暂停队列
原型:/use/include/dispatch/object.h
1 | /** |
dispatch_resume全局方法
说明:重启队列
原型:/use/include/dispatch/object.h
1 | /** |
1 | // 暂停队列 |
14.2.3 操作队列
说明:有一些称为
操作(operation)
的API
,可以让队列使用起来更加简单。
- 创建一个
操作
对象- 将其指派给
操作队列
操作
被队列执行
操作
的创建方式
说明:一共有3种方式
方式 | 说明 |
---|---|
调用操作(NSInvocationOperation ) |
前提是已经拥有一个可以完成工作的类,并且想在队列上执行 |
代码块操作(NSBlockOperation ) |
类似包含了要执行代码块的dispatch_async 函数 |
自定义的操作 |
通过继承NSOperation 定义自己的操作 |
方式一:创建调用操作
1 | @implementation MyCustomClass |
方式二:创建代码块操作
说明:创建时作为参数的
代码块
的类型和在调度队列
中使用的相同。
- 一旦创建了第一个
代码块操作
,便可以通过addExecutionBlock
方法继续添加更多的代码块
- 根据
队列
的类型,代码块会分别以连续或并行的方式运行
1 | NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock: ^ { |
向队列中添加操作
说明:可以使用
NSOperationQueue
来取代之前使用的dispatch_queue
函数,特点如下
- 并发执行
操作
- 具有
相关性
,也就是说,如果某个操作
是基于其它操作
的,则也会先被执行技巧:如果要确保添加的
操作
是连续执行(串行)的,可以设置最大并发操作数为1,这样会按照先入先出
的规范执行
1 | // 获取队列 |